# ----------------------------------------------------------------------
# ClickHouse Dictionaries
# ----------------------------------------------------------------------
# Copyright (C) 2007-2020 The NOC Project
# See LICENSE for details
# ----------------------------------------------------------------------

# Python modules
import os
import operator

# NOC modules
from .fields import BaseField

__all__ = ["Dictionary"]


class DictionaryBase(type):
    def __new__(mcs, name, bases, attrs):
        cls = type.__new__(mcs, name, bases, attrs)
        cls._fields = {}
        cls._tsv_encoders = {}
        cls._meta = DictionaryMeta(
            name=getattr(cls.Meta, "name", None),
            layout=getattr(cls.Meta, "layout", None),
            lifetime_min=getattr(cls.Meta, "lifetime_min", 3600),
            lifetime_max=getattr(cls.Meta, "lifetime_max", 3600),
        )
        assert cls._meta.layout in (None, "flat", "hashed")
        for k in attrs:
            if isinstance(attrs[k], BaseField):
                cls._meta.register_field(k, attrs[k])
        # Fill _meta.ordered_fields
        cls._meta.order_fields()
        return cls


class DictionaryMeta(object):
    def __init__(self, name=None, layout=None, lifetime_min=None, lifetime_max=None):
        self.name = name
        self.layout = layout
        self.lifetime_min = lifetime_min
        self.lifetime_max = lifetime_max
        self.fields = {}  # name -> field
        self.ordered_fields = []  # fields in order

    def register_field(self, name, field):
        self.fields[name] = field
        field.set_name(name)

    def get_field(self, name):
        return self.fields[name]

    def order_fields(self):
        """
        Fill `ordered_fields`
        :return:
        """
        self.ordered_fields = list(
            sorted(self.fields.values(), key=operator.attrgetter("field_number"))
        )


class Dictionary(object, metaclass=DictionaryBase):
    class Meta:
        name = None
        layout = None
        lifetime_min = None
        lifetime_max = None

    _collection = None
    _seq = None

    @classmethod
    def iter_cls(cls):
        """
        Generator yielding all known dictionaries
        :return:
        """
        for f in os.listdir("core/bi/dictionaries"):
            if not f.endswith(".py") or f.startswith("_"):
                continue
            yield Dictionary.get_dictionary_class(f[:-3])

    @classmethod
    def get_config(cls):
        """
        Generate XML config
        :return:
        """
        x = [
            "<dictionaries>",
            "    <comment>Generated by NOC, do not change manually</comment>",
            "    <dictionary>",
            "        <name>%s</name>" % cls._meta.name,
            "        <lifetime>",
            "            <min>%s</min>" % cls._meta.lifetime_min,
            "            <max>%s</max>" % cls._meta.lifetime_max,
            "        </lifetime>",
            "        <layout>",
            "            <%s />" % cls._meta.layout,
            "        </layout>",
            "        <source>",
            "            <http>",
            '                <url>http://{{ range $index, $element := service "datasource~_agent"}}{{if eq $index 0}}{{.Address}}:{{.Port}}{{end}}{{else}}127.0.0.1:65535{{ end }}/api_datasource/ch_%s.tsv</url>'
            % cls._meta.name,
            "                <format>TabSeparated</format>",
            "            </http>",
            "        </source>",
            "        <structure>",
            "             <id>",
            "                 <name>bi_id</name>",
            "             </id>",
            "             <attribute>",
            "                 <name>id</name>",
            "                 <type>String</type>",
            "                 <hierarchical>false</hierarchical>",
            "             </attribute>",
        ]
        for field in cls._meta.ordered_fields:
            hier = getattr(field, "is_self_reference", False)
            x += [
                "             <attribute>",
                "                 <name>%s</name>" % field.name,
                "                 <type>%s</type>" % field.get_db_type(),
                "                 <null_value>Unknown</null_value>",
                "                 <hierarchical>%s</hierarchical>" % ("true" if hier else "false"),
                "             </attribute>",
            ]
        x += ["        </structure>", "    </dictionary>", "</dictionaries>"]
        return "\n".join(x)

    @classmethod
    def get_dictionary_class(cls, name):
        """
        Returns dictionary class referred by name
        @todo: Process custom/
        :param name:
        :return:
        """
        m = __import__("noc.core.bi.dictionaries.%s" % name, {}, {}, "*")
        for a in dir(m):
            o = getattr(m, a)
            if not hasattr(o, "_meta"):
                continue
            if getattr(o._meta, "name", None) == name:
                return o
        return None

    @classmethod
    def get_field_type(cls, name):
        """
        Returns field type

        :param name:
        :return:
        """
        return cls._meta.fields[name].get_db_type()

    @classmethod
    def get_pk_name(cls):
        """
        Get primary key's name

        :return: Field name
        """
        return cls._meta.ordered_fields[0].name

    @classmethod
    def dump(cls, out):
        # @todo: !!!
        raise NotImplementedError()
        for d in cls.get_collection().find({}):
            out.write(
                "%s\t%s\n"
                % (
                    str(d["id"]),
                    "\t".join(
                        str(d.get(f, "")).replace("\n", "").replace("\t", "") for f in cls._fields
                    ),
                )
            )
